package com.wyw.hemerocallis.service.impl;

import com.alibaba.fastjson.JSON;
import com.wyw.hemerocallis.constant.AuthorityConstant;
import com.wyw.hemerocallis.constant.CommonConstant;
import com.wyw.hemerocallis.entity.HemerocallisUser;
import com.wyw.hemerocallis.repository.HemerocallisUserRepository;
import com.wyw.hemerocallis.service.IJWTService;
import com.wyw.hemerocallis.vo.LoginUserInfo;
import com.wyw.hemerocallis.vo.UsernameAndPassword;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import sun.misc.BASE64Decoder;

import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.time.LocalDate;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.temporal.ChronoUnit;
import java.util.Date;
import java.util.UUID;

/**
 * JWT 相关服务接口实现
 *
 * @author Mr Wu    yewen.wu.china@gmail.com
 * <p>
 * Update History:
 * Author        Time            Content
 */
@Slf4j
@Service
@Transactional(rollbackFor = Exception.class)
@RequiredArgsConstructor
public class IJWTServiceImpl implements IJWTService {

    private final HemerocallisUserRepository hemerocallisUserRepository;

    @Override
    public String generateToken(String username, String password) throws Exception {
        return generateToken(username, password, 0);
    }

    @Override
    public String generateToken(String username, String password, int expireDay) throws Exception {
        // 首先需要验证用户是否能够通过授权校验, 即输入的用户名和密码能否匹配数据表记录
        HemerocallisUser hemerocallisUser = hemerocallisUserRepository.findByUsernameAndPassword(username, password);

        if(null == hemerocallisUser){
            log.error("获取用户信息失败，username：{}，password：{}", username, password);
            return null;
        }

        // Token 中塞入对象, 即 JWT 中存储的信息, 后端拿到这些信息就可以知道是哪个用户在操作
        LoginUserInfo loginUserInfo = new LoginUserInfo(hemerocallisUser.getId(), hemerocallisUser.getUsername());

        if(expireDay <= 0){
            expireDay = AuthorityConstant.DEFAULT_EXPIRE_DAY;
        }

        //计算超时时间
        ZonedDateTime zdt = LocalDate.now().plus(expireDay, ChronoUnit.DAYS)
                .atStartOfDay(ZoneId.systemDefault());
        Date expireDate = Date.from(zdt.toInstant());

        return Jwts.builder()
                // jwt payload --> KV
                .claim(CommonConstant.JWT_USER_INFO_KEY, JSON.toJSONString(loginUserInfo))
                // jwt id
                .setId(UUID.randomUUID().toString())
                // jwt 过期时间
                .setExpiration(expireDate)
                // jwt 签名 --> 加密
                .signWith(getPrivateKey(), SignatureAlgorithm.RS256)
                .compact();
    }

    @Override
    public String registerUserAndGenerateToken(UsernameAndPassword usernameAndPassword) throws Exception {
        // 先去校验用户名是否存在, 如果存在, 不能重复注册
        HemerocallisUser oldUser = hemerocallisUserRepository.findByUsername(
                usernameAndPassword.getUsername());
        if (null != oldUser) {
            log.error("用户名已经注册了: {}", oldUser.getUsername());
            return null;
        }

        HemerocallisUser hemerocallisUser = new HemerocallisUser();
        hemerocallisUser.setUsername(usernameAndPassword.getUsername());
        hemerocallisUser.setPassword(usernameAndPassword.getPassword());   // MD5 编码以后
        hemerocallisUser.setExtraInfo("{}");

        // 注册一个新用户, 写一条记录到数据表中
        hemerocallisUser = hemerocallisUserRepository.save(hemerocallisUser);
        log.info("注册用户成功: {}, {}", hemerocallisUser.getUsername(),
                hemerocallisUser.getId());

        // 生成 token 并返回
        return generateToken(hemerocallisUser.getUsername(), hemerocallisUser.getPassword());
    }

    /**
     * 根据本地存储的私钥获取到 PrivateKey 对象
     * */
    private PrivateKey getPrivateKey() throws Exception {

        PKCS8EncodedKeySpec priPKCS8 = new PKCS8EncodedKeySpec(
                new BASE64Decoder().decodeBuffer(AuthorityConstant.PRIVATE_KEY));
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        return keyFactory.generatePrivate(priPKCS8);
    }

}
